﻿using System;
using Shell.Build;
using Shell.Build.Lifetime;
using Shell.Build.TypeMapping;

namespace Shell
{
    internal class DefaultBahaviorExtension : IShellExtension
    {
        public Guid Identity
        {
            get { return IShellExtensionIdentities.DefaultBahaviorExtension; }
        }

        public ShellExtensionContext Context { get; private set; }

        public void Initialize(ShellExtensionContext context)
        {
            this.Context = context;
            this.Context.TypeRegistered += this.Context_TypeRegistered;
            this.Context.InstanseRegistered += this.Context_InstanseRegistered;
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.Context.TypeRegistered -= this.Context_TypeRegistered;
                this.Context.InstanseRegistered -= this.Context_InstanseRegistered;
                this.Context = null;
            }
        }

        private void Context_InstanseRegistered(object sender, InstanceRegisterEventArgs e)
        {
            // check all parameters & reset them if we need
            var instanseType = e.Instance.GetType();
            var fromType = e.From ?? instanseType;
            var lifetime = e.Lifetime ?? new SingletonLifttimePolicy();

            // build identity of the original instanse
            var instanseIdentity = new BuildIdentity(instanseType, e.Name);
            // build identity of the from type
            var fromIdentity = new BuildIdentity(fromType, e.Name);

            // set instance & register the lifetime policy with the original instance build key
            lifetime.Set(e.Instance);
            this.ProcessLifetime(e.Lifetime, instanseIdentity);

            // register type mapping policy with the type from build key
            var policy = new DefaultTypeMappingPolicy(instanseIdentity);
            this.Context.BuildPolicies.Set<ITypeMappingPolicy>(fromIdentity, policy);
            e.Lifetime.Set(e.Instance);
        }

        private void Context_TypeRegistered(object sender, TypeRegisterEventArgs e)
        {
            if (e.From != null)
            {
                // create the target build identity for below using
                var identity = new BuildIdentity(e.To, e.Name);

                // create different build policy for normal & generic types
                IBuildPolicy policy;
                if (e.From.IsGenericTypeDefinition && e.To.IsGenericTypeDefinition)
                {
                    policy = new GenericTypeBuildKeyMappingPolicy(identity);
                }
                else
                {
                    policy = new DefaultTypeMappingPolicy(identity);
                }

                // if there is a lifetime policy provided, we gotta process it here
                if (e.Lifetime != null)
                {
                    this.ProcessLifetime(e.Lifetime, identity);
                }
                var identityFromType = new BuildIdentity(e.From, e.Name);
                this.Context.BuildPolicies.Set<ITypeMappingPolicy>(new BuildIdentity(e.From, e.Name), policy);
            }
        }

        private void ProcessLifetime(ILifetimePolicy lifetime, BuildIdentity identity)
        {
            if (identity.Type.IsGenericTypeDefinition)
            {
                var factory = new LifetimeManagerFactory(Context, identity.Type);
                this.Context.BuildPolicies.Set<ILifetimePolicyFactory>(identity, factory);
            }
            else
            {
                this.Context.BuildPolicies.Set<ILifetimePolicy>(identity, lifetime);
                if (lifetime is IDisposable)
                {
                    Context.LifetimeContainer.Add(lifetime);
                }
            }
        }

    }
}
